package edu.pku.sei.transformation.structure;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import edu.pku.sei.mdabase.core.IDataType;
import edu.pku.sei.mdabase.infrastructure.metamodel.MetaModelGraph;
import edu.pku.sei.mdabase.infrastructure.model.MModelGraph;
import edu.pku.sei.mdabase.infrastructure.runtime.Context;
import edu.pku.sei.mdabase.infrastructure.runtime.Environment;
import edu.pku.sei.mdabase.infrastructure.runtime.Variable;
import edu.pku.sei.transformation.pattern.TPatternEdge;
import edu.pku.sei.transformation.pattern.TPatternGraph;
import edu.pku.sei.transformation.pattern.TPatternNode;
import edu.pku.sei.transformation.semantics.TransformationController;
import edu.pku.sei.transformation.visitor.MTControllerVisitor;

public class RuleBasedEnvironment extends Environment {
	
	private Transformation transformation;
	private Context globalContext;
	private MTControllerVisitor mtvisitor;
	private List<TypedModel> targetmodels = new ArrayList<TypedModel>();
	private Map<TypedModel, MModelGraph> modelMap = new HashMap<TypedModel, MModelGraph>(); 
	
	private Map<String, Rule> rules = new HashMap<String, Rule>();
	
	public void registerModel(TypedModel typedmodel, MModelGraph model) 
	{
		String name = typedmodel.getName();
		modelMap.put(typedmodel, model);
		putCachedData(model.getManager(), "ModelManager", typedmodel);
	}
	
	public MModelGraph getModelByTypedModel(TypedModel tm)
	{
		return modelMap.get(tm);
	}
	
	public void setMTVisitor(MTControllerVisitor mt)
	{
		this.mtvisitor = mt;
	}
	
	public MTControllerVisitor getMTVisitor()
	{
		return mtvisitor;
	}
	
	public void addTargetmodel(TypedModel tm)
	{
		targetmodels.add(tm);
	} 
	
	public Transformation getTransformation()
	{
		return transformation;
	}
	
	public void setTransformation(Transformation transformation)
	{
		this.transformation = transformation;
	}
	
	public Context getGlobalContext()
	{
		return globalContext;
	}
	
	public void setGlobalContext(Context context)
	{
		this.globalContext = context;
	}
	
	public Map<String, Rule> getRules()
	{
		return rules;
	}
	
	public void setRules(Map<String, Rule> rules)
	{
		this.rules = rules;
	}
	
	public boolean registRule(String name, Rule rule)
	{
		if (rules.containsKey(name))
			return false;
		rules.put(name, rule);
		return true;
	}
	
	public List<Domain> getSourceDomains(Rule rule)
	{
		List<Domain> result = (List<Domain>) getCachedData("SourceDomains", rule);
		if (result != null)
			return result;
		result = new ArrayList<Domain>();
		result.addAll(rule.getDomains());
		result.removeAll(getTargetDomains(rule));
		putCachedData(result, "SourceDomains", rule);
		return result;
	}
	
	public List<Domain> getTargetDomains(Rule rule)
	{
		List<Domain> result = (List<Domain>) getCachedData("TargetDomains", rule);
		if (result != null)
			return result;
		result = new ArrayList<Domain>();
		for (int i = 0; i < targetmodels.size(); i++)
		{
			List<Domain> domains = rule.getDomains(targetmodels.get(i));
			assert(domains != null);
			result.addAll(domains);
		}
		putCachedData(result, "TargetDomains", rule);
		return result;
	}

	@Override
	public Object[] getTypeByPrefixName(String name) {
		int sp = name.indexOf("::");
		
		if(sp==-1)
			return null;
		
		String prefix = name.substring(0, sp);
		String typeName = name.substring(sp+2);
		
		Object type = typeMap.get(prefix);
		if(type==null)
			return null;
		
		if(type instanceof MetaModelGraph) {
			MetaModelGraph metamodel = (MetaModelGraph)type;
			return new Object[] {prefix, metamodel.getMetaClass(typeName)};
		} else if(type instanceof TypedModel){
			MetaModelGraph metamodel = ((TypedModel)type).getType().getMetamodel();
			return new Object[] {prefix, metamodel.getMetaClass(typeName)};
		}
		else {
			System.out.println("unknown type "+name);
			return null;
		}
	}
	
	public List<Variable> getBoundedVariablesFromContext(Domain domain, Context base)
	{
		List<Variable> result = new ArrayList<Variable>();
		if (domain instanceof PatternDomain)
		{
			PatternDomain pd = (PatternDomain)domain;
			TPatternGraph pattern = pd.getPattern();
			for (TPatternNode node:pattern.getNodes())
			{
				String name = node.getName();
				if (!node.getType().isPrimitive())
				{
					Variable variable = base.getVariable(name);
					result.add(variable);
				}
			}
			for (TPatternEdge edge:pattern.getEdges())
			{
				String name = edge.getName();
				if (!edge.getType().isPrimitive())
				{
					Variable variable = base.getVariable(name);
					result.add(variable);
				}
			}
		}
		else
		{
			List<String> varnames = TransformationController.instance.getBoundVariables(domain, this);
			for (String name : varnames)
			{
				Variable variable = base.getVariable(name);
				result.add(variable);
			}
		}
		return result;
	}
	
	@Override
	public Object invokeRule(String name, Context base, String mode)
	{
		return mtvisitor.invokeSemantic(name, base, mode);
	}
	
	@Override
	public List<String> getParamList(String ruleName)
	{
		List<String> result = new ArrayList<String>();
		Rule rule = getRules().get(ruleName);
		for (PatternDomain domain : rule.getDomains())
		{
			result.add(domain.getPattern().getNodes().get(0).getName());
		}
		return result;
	}
	
	@Override
	public void initContext(Context base, Context toInit, String ruleName)
	{
		Rule rule = getRules().get(ruleName);
		TransformationController.instance.registVariable(toInit, rule);
		//toInit.setParentContext(base);
		toInit.setEnvironment(base.getEnvironment());
	}
}
